Skip to content

[#885] Bound the create of the tree catalog, and say when an engine has no bound to give - #1003

Open
vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issues/885-catalog-ddl-bound
Open

[#885] Bound the create of the tree catalog, and say when an engine has no bound to give#1003
vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issues/885-catalog-ddl-bound

Conversation

@vharseko

@vharseko vharseko commented Sep 9, 2026

Copy link
Copy Markdown
Member

Part of #885, on top of #936 (merged as 21d03d5): withDdlLockBound() is that PR's code. Rebased onto master after that merge - the diff here is one commit, two files.

Problem

#936 bounded the DDL of this backend by putting withDdlLockBound() on the two paths its DDL goes through - commitStatement(sql, ddl==true) and the drop loop of a removed backend. There is a third, and it is neither of them.

createCatalogTable() issues its create table on the catalog's own connection, before openTree() enrols the tree it is opening. That statement:

So after #934 and #936 it is the one statement of this backend with no bound of any kind. Where it runs makes that worse rather than better: it is on the open path, and it runs inside synchronized (catalogLock) - a wait there parks every other thread of the storage that wants the catalog, not only the one that queued.

Nor does that wait need an exotic database. The method's own comment names the case: "a table that turned up between the lookup and this statement is what was wanted, whoever made it" - an offline tool beside a running server, the pair #888 is about. On postgres a second session creating that same table inside a transaction it has not committed makes this one wait on that transaction, lock_timeout being 0 by default; a lock held on the schema does the same.

It arrived after both were written: createCatalogTable() came with #893, which merged the same morning #934 did, and #936 took it in through its merge of master without the bound reaching it.

Change

1. The create of the catalog table runs under withDdlLockBound(), like every other DDL of this backend. The commit is inside the bound, because on postgres it is that commit which ends the transaction a set local belongs to.

Everything the bound leans on already works on this connection: physical() passes a raw connection through, dialectOf() reads the driver name off it, and restoreDdlLockBound() already asks instanceof CachedConnection before keeping a connection out of a pool. What did not fit is what two comments claim, this being the one connection reaching that code which was never in the pool: the javadoc of withDdlLockBound() and the "left behind" warning of restoreDdlLockBound() now say what becomes of the catalog's own - it is closed with the write that opened it, so a setting left on it reaches the rest of that write and nothing after it.

A lock this create gives up on reaches the operator named: gaveUpOnTheLock() rewrites it, the existing catch carries it into the StorageRuntimeException saying which table the backend wanted and why, and the property is in that chain. Unbounded, it arrived as a bare 55P03 inside a message about privileges - which is the wrong thing to go and check.

2. An engine this backend knows no lock setting for is reported, once. The early return of withDdlLockBound() had three reasons behind one condition; they are three conditions now, and only the middle one speaks:

  • seconds<=0 - the deployment asked for no bound, and there is nothing to say;
  • dialect==null - reported, naming the class of the driver;
  • ddlLockBoundSql(seconds,null)==null - oracle, left alone deliberately and documented as such.

dialectOf() keys on the driver class name, so this is not the engine of an exotic database alone: MariaDB Connector/J - or a Percona- or Aurora-branded driver - against a live MySQL answers null here, and that is a session whose lock_wait_timeout is a year, which is the wait this bound exists to end. Leaving the bound off there stays, and testAnEngineThisBackendDoesNotKnowIsLeftAlone still pins it: untested SQL is no thing to send a database on the path a backend opens by. Only the silence goes, for the reason the strict parsing of the property exists - a deployment that asked for a bound is never quietly left with none. reportUnknownDialect() in CachedConnection cannot cover this one: it keys on the url, which such a driver reads as a mysql one, and it speaks of the connect bound and pool.timeout. The driver is named rather than the url, since the driver is what this reads and what a deployment would change - and a url carries the password of the account the backend works as.

The javadoc of ddl.lock.timeout now names this case beside the oracle exemption, that being what an operator reads after meeting a DDL which waited anyway.

Testing

JDBCDdlLockBoundTestCase gains six cases: what postgres and what mysql are told around the create of the catalog table (the readback and the value given back among them), the lock it gave up on naming the property out to the caller, and the three placements of the report - the unknown engine, the bound nobody asked for, and the oracle left alone on purpose.

mvn -o -pl opendj-server-legacy -am -Pprecommit -Dfailsafe.failIfNoSpecifiedTests=false verify
    -Dit.test='JDBCDdlLockBoundTestCase,CatalogConnectionTestCase,CachedConnectionTestCase,JDBCStatementBoundTestCase,JDBCStorageRetryTest,StampConnectionTestCase'

306 tests, 0 failures, 0 errors, 0 skips, on the rebase onto master (21d03d5) - the two cases the last commit of #936 added to JDBCDdlLockBoundTestCase among them.

Passing is not enough on its own, so every guard was built with its defect put back:

variant result
the bound taken off the catalog create testTheCreateOfTheCatalogTableIsBounded (the set local lock_timeout = 5000 gone from what postgres was told), ...GivesMysqlItsValueBack (expected [4] but found [2]) and ...GaveUpOnNamesTheProperty (the lock reaches the operator as the bare 55P03 it arrived as)
the report moved in front of the property guard testAWaitNobodyAskedToBoundIsNotReportedAsAnUnknownEngine fails, expected [false] but found [true]
the report moved onto the branch that leaves oracle alone testOracleIsNotReportedAsAnEngineThisBackendDoesNotKnow fails the same way, and testAnEngineThisBackendDoesNotKnowIsReported with it

One thing the new cases needed, and the suite is the better for it: the storage of a case now answers newStampConnection() itself. Reaching openTree() means reaching the stamp of #866, whose connect would otherwise go to DriverManager - which registers every JDBC driver on the classpath. This suite needs no database and should touch none: reuseForks=false keeps that from reaching another class of this build, but it reaches one in an IDE, where CatalogConnectionTestCase needs its own probe driver registered ahead of pgjdbc.

Out of scope

@vharseko
vharseko requested a review from maximthomas September 9, 2026 15:54
@vharseko vharseko added bug jdbc tests Test suites: fixing, enabling, un-disabling concurrency Thread-safety / race-condition bugs labels Sep 9, 2026
…say when an engine has no bound to give

The create table of the tree catalog is the DDL of this backend that reaches
neither commitStatement() nor the drop loop: openTree() issues it on the
catalog's own connection before it enrols the tree it is opening, so the bound
of this branch never covered it. It is BULK, so it carries no query timeout, and
the standing read bound of OpenIdentityPlatform#934 is lifted for the length of an unbounded
statement - which left it the one statement of this backend with no bound of any
kind, on the open path, inside the monitor every other thread of the storage
queues on.

It now runs under withDdlLockBound() like every other DDL, with the commit
inside the bound: on postgres that commit is what ends the transaction a
"set local" belongs to. That connection is not the pool's, so the two places
claiming a connection reaching this code is pooled say what happens to the
catalog's own instead - it is closed with the write that opened it.

And an engine behind a driver this backend knows no lock setting for is now
reported once rather than left silent. dialectOf() keys on the class name of the
driver, so a mariadb, percona or aurora driver against a live mysql answers null
- a session whose lock_wait_timeout is a year, which is the wait this bound
exists to end. Leaving the bound off there stays; only the silence goes, for the
reason the strict parsing of the property exists. CachedConnection cannot say
it: it keys on the url, which such a driver reads as a mysql one.

JDBCDdlLockBoundTestCase gains six cases: what postgres and mysql are told
around the create of the catalog table, the lock it gives up on naming the
property out to the operator, and the three placements of the report - the
unknown engine, the bound nobody asked for, and the oracle left alone on
purpose. The suite is mock-only and now stays that way: the storage of a case
answers newStampConnection() itself rather than letting it reach DriverManager.
@vharseko
vharseko force-pushed the issues/885-catalog-ddl-bound branch from 1c40760 to a4b4934 Compare September 11, 2026 05:14
@vharseko

Copy link
Copy Markdown
Member Author

@maximthomas rebased onto master now that #936 is in (21d03d5): the diff is the one commit a4b4934, two files, as the description said it would be.

One conflict, in JDBCDdlLockBoundTestCase: the last commit of #936 (9cc5a0e - testALockTheLookupOfAClearGaveUpOnNamesTheProperty, testAFailureOfTheLookupThatWasNoLockWaitIsLeftExactlyAsItIs and givingUpOnTheLookup()) and this one added their cases at the same spot of the file. Both kept, one after the other, nothing else touched; JDBCStorage merged clean.

The run from the description, on the rebased branch: 306 tests, 0 failures, 0 errors, 0 skips - 306 rather than 271, these classes having gained cases on master since.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug concurrency Thread-safety / race-condition bugs jdbc tests Test suites: fixing, enabling, un-disabling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant